package com.houhanzhi.service.impl;

import com.houhanzhi.component.ShortLinkComponent;
import com.houhanzhi.config.RabbitMQConfig;
import com.houhanzhi.constant.RedisKey;
import com.houhanzhi.controller.request.*;
import com.houhanzhi.enums.BizCodeEnum;
import com.houhanzhi.enums.DomainTypeEnum;
import com.houhanzhi.enums.EventMessageTypeEnum;
import com.houhanzhi.enums.ShortLinkStateEnum;
import com.houhanzhi.feign.TrafficFeignService;
import com.houhanzhi.interceptor.LoginInterceptor;
import com.houhanzhi.manager.DomainManager;
import com.houhanzhi.manager.GroupCodeMappingManager;
import com.houhanzhi.manager.LinkGroupManager;
import com.houhanzhi.manager.ShortLinkManager;
import com.houhanzhi.model.*;
import com.houhanzhi.response.JsonData;
import com.houhanzhi.service.ShortLinkService;
import com.houhanzhi.util.CommonUtil;
import com.houhanzhi.util.IDUtil;
import com.houhanzhi.util.JsonUtil;
import com.houhanzhi.vo.ShortLinkVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author houhanzhi
 * @since 2025-02-26
 */
@Service
@Slf4j
public class ShortLinkServiceImpl implements ShortLinkService {

    @Autowired
    private ShortLinkManager shortLinkManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private ShortLinkComponent shortLinkComponent;

    @Autowired
    private DomainManager domainManager;

    @Autowired
    private LinkGroupManager linkGroupManager;

    @Autowired
    private GroupCodeMappingManager groupCodeMappingManager;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private TrafficFeignService trafficFeignService;

    /**
     * 解析短链
     *
     * @param shortLinkCode
     * @return
     */
    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {
        ShortLinkDO shortLinkDO = shortLinkManager.findByShortLinkCode(shortLinkCode);
        if (Objects.isNull(shortLinkDO)) {
            return null;
        }
        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(shortLinkDO, shortLinkVO);
        return shortLinkVO;
    }

    /**
     * 生产者：创建短链
     *
     * @param request
     * @return
     */
    @Override
    public JsonData createShortLink(ShortLinkAddRequest request) {

        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        /**
         * 预扣减流量包(此处还可用分段锁，不针对商品加锁，针对库存加锁，根据地理位置将商品库存划分为N块，分段加锁)
         */
        // 需要预先检查下是否有足够多的可以进行创建
        String cacheKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        //检查key是否存在，然后递减，是否大于等于0，使用lua脚本
        // 如果key不存在，则未使用过(免费流量包)，lua返回值是0； 新增流量包的时候，不用重新计算次数，直接删除key,消费的时候回计算更新
        String script = "if redis.call('get',KEYS[1]) then return redis.call('decr',KEYS[1]) else return 0 end";
        Long leftTimes = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(cacheKey), "");
        log.info("今日流量包剩余次数:{}", leftTimes);

        // 预扣减流量包成功，发送MQ消息，创建短链
        if (leftTimes >= 0) {
            // 添加原始url前缀
            String newOriginalUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());
            request.setOriginalUrl(newOriginalUrl);

            EventMessage eventMessage = EventMessage.builder()
                    .messageId(IDUtil.geneSnowFlakeID().toString())
                    .eventMessageType(EventMessageTypeEnum.SHORT_LINK_ADD.name())
                    .accountNo(accountNo)
                    .content(JsonUtil.obj2Json(request))
                    .build();

            rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);
            return JsonData.buildSuccess();
        } else {
            // 流量包不足
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
    }


    /**
     * 处理新增短链消息
     * //判断短链域名是否合法
     * //判断组名是否合法
     * //生成长链摘要
     * //生成短链码
     * //加锁
     * //查询短链码是否存在
     * //构建短链对象
     * //保存数据库
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handlerAddShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkAddRequest addRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);
        // 判断短链域名是否合法
        final DomainDO domainDO = checkDomain(addRequest.getDomainType(), addRequest.getDomainId(), accountNo);
        // 判断短链组名是否合法
        final LinkGroupDO linkGroupDO = checkLinkGroup(addRequest.getGroupId(), accountNo);
        // 生成长链摘要
        final String originalUrlDigest = CommonUtil.MD5(addRequest.getOriginalUrl());
        // 生成短链码
        final String shortLinkCode = shortLinkComponent.createShortLinkCode(addRequest.getOriginalUrl());

        // 加锁：lua+redis
        //key1是短链码，ARGV[1]是accountNo,ARGV[2]是过期时间(秒)
        String script = "if redis.call('EXISTS',KEYS[1])==0 then " +
                "redis.call('set',KEYS[1],ARGV[1]); " + // 加锁成功
                "redis.call('expire',KEYS[1],ARGV[2]); " + // 设置过期时间
                "return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then " +
                "return 2;" + // 同一用户，同一业务线，不同服务，加锁成功，可重入
                " else return 0; " + // 加锁失败
                "end;";

        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);

        // 短链码重复标记
        boolean duplicateCodeFlag = false;

        // 加锁成功
        if (result > 0) {
            if (EventMessageTypeEnum.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(eventMessageType)) {
                // C端处理
                // 判断短链码是否被占用
                ShortLinkDO shortLinkCodeDO = shortLinkManager.findByShortLinkCode(shortLinkCode);
                if (Objects.isNull(shortLinkCodeDO)) {
                    // 扣减流量包，进行 rpc 调用
                    boolean reduceFlag = reduceTraffic(eventMessage, shortLinkCode);
                    // 扣减成功，才能创建短链
                    if (reduceFlag) {
                        ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                                .accountNo(accountNo)
                                .code(shortLinkCode)
                                .title(addRequest.getTitle())
                                .originalUrl(addRequest.getOriginalUrl())
                                .domain(domainDO.getValue())
                                .groupId(linkGroupDO.getId())
                                .expired(addRequest.getExpired())
                                .sign(originalUrlDigest)
                                .state(ShortLinkStateEnum.ACTIVE.name())
                                .del(0)
                                .build();
                        shortLinkManager.addShortLink(shortLinkDO);
                        return true;
                    }
                } else {
                    log.error("C端短链码重复：{}", eventMessage);
                    duplicateCodeFlag = true;
                }
            } else if (EventMessageTypeEnum.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
                // B端处理
                // 判断短链码是否被占用
                GroupCodeMappingDO groupCodeMappingDOInDB = groupCodeMappingManager.findByCodeAndGroupId(shortLinkCode, linkGroupDO.getId(), accountNo);
                if (Objects.isNull(groupCodeMappingDOInDB)) {
                    // 构建短链对象
                    GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(addRequest.getTitle())
                            .originalUrl(addRequest.getOriginalUrl())
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired())
                            .sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVE.name())
                            .del(0)
                            .build();
                    groupCodeMappingManager.add(groupCodeMappingDO);
                    return true;
                } else {
                    log.error("B端短链码重复：{}", eventMessage);
                    duplicateCodeFlag = true;
                }
            }
        } else { //加锁失败，自旋100ms，再调用，失败的原因可能是短链码已经被占用，需要重新生成
            log.error("加锁失败: {}", eventMessage);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            duplicateCodeFlag = true;
        }

        // 自旋
        if (duplicateCodeFlag) {
            // 递增版本号，重新生成短链码
            final String newOriginalUrl = CommonUtil.addUrlPrefixVersion(addRequest.getOriginalUrl());
            addRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setContent(JsonUtil.obj2Json(addRequest));
            log.warn("短链码报错失败，重新生成:{}", eventMessage);
            handlerAddShortLink(eventMessage);
        }
        return false;
    }

    /**
     * 扣减流量包
     *
     * @param eventMessage
     * @param shortLinkCode
     * @return
     */
    private boolean reduceTraffic(EventMessage eventMessage, String shortLinkCode) {
        //处理流量包扣减
        //根据短链类型，检查是否有足够多的流量包  分布式事务问题
        UseTrafficRequest useTrafficRequest = UseTrafficRequest.builder()
                .accountNo(eventMessage.getAccountNo())
                .bizId(shortLinkCode)
                .build();

        JsonData jsonData = trafficFeignService.useTraffic(useTrafficRequest);
        if(jsonData.getCode() != 0) {
            log.error("流量包不足，扣减失败操:{}", eventMessage);
            return false;
        }
        return true;
    }


    /**
     * 校验短链域名
     *
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {
        DomainDO domainDO = new DomainDO();
        if (DomainTypeEnum.CUSTOM.name().equalsIgnoreCase(domainType)) {
            domainDO = domainManager.findById(domainId, accountNo);
        } else {
            domainDO = domainManager.findByDomainTypeAndId(domainId, DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO, "短链域名不合法");
        return domainDO;
    }

    /**
     * 校验短链组名
     *
     * @param groupId
     * @param accountNo
     * @return
     */
    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {
        final LinkGroupDO detail = linkGroupManager.detail(groupId, accountNo);
        Assert.notNull(detail, "短链组名不合法");
        return detail;
    }

    /**
     * B端：分页查找短链
     *
     * @param request
     * @return
     */
    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        return groupCodeMappingManager.pageShortLinkByGroupId(request.getPage(), request.getSize(), request.getGroupId(), accountNo);
    }


    /**
     * 生产者：删除短链
     *
     * @param request
     * @return
     */
    @Override
    public JsonData delShortLink(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        EventMessage eventMessage = EventMessage.builder()
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageTypeEnum.SHORT_LINK_DEL.name())
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .build();

        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkDelRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    /**
     * 生产者：更新短链
     *
     * @param request
     * @return
     */
    @Override
    public JsonData updateShortLink(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageTypeEnum.SHORT_LINK_UPDATE.name())
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .build();

        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkUpdateRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }


    /**
     * 处理更新短链消息
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handlerUpdateShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkUpdateRequest shortLinkUpdateRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkUpdateRequest.class);
        DomainDO domainDO = checkDomain(shortLinkUpdateRequest.getDomainType(), shortLinkUpdateRequest.getDomainId(), accountNo);

        if (EventMessageTypeEnum.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(eventMessageType)) {
            final ShortLinkDO shortLinkDO = ShortLinkDO.builder().code(shortLinkUpdateRequest.getCode())
                    .title(shortLinkUpdateRequest.getTitle())
                    .domain(domainDO.getValue())
                    .accountNo(accountNo)
                    .build();
            int rows = shortLinkManager.update(shortLinkDO);
            log.debug("更新C端短链: rows={}", rows);
            return true;

        } else if (EventMessageTypeEnum.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
            final GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().id(shortLinkUpdateRequest.getMappingId())
                    .groupId(shortLinkUpdateRequest.getGroupId())
                    .accountNo(accountNo)
                    .title(shortLinkUpdateRequest.getTitle())
                    .domain(domainDO.getValue())
                    .build();
            int rows = groupCodeMappingManager.update(groupCodeMappingDO);
            log.debug("更新B端短链: rows={}", rows);
            return true;
        }
        return false;
    }


    /**
     * 处理删除短链消息
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handlerDelShortLink(EventMessage eventMessage) {

        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkDelRequest shortLinkDelRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkDelRequest.class);

        if (EventMessageTypeEnum.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(eventMessageType)) {
            int rows = shortLinkManager.del(shortLinkDelRequest.getCode(), accountNo);
            log.debug("删除B端短链: rows={}", rows);
            return true;
        } else if (EventMessageTypeEnum.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().groupId(shortLinkDelRequest.getGroupId())
                    .accountNo(accountNo)
                    .code(shortLinkDelRequest.getCode())
                    .build();
            int rows = groupCodeMappingManager.del(groupCodeMappingDO);
            log.debug("删除B端短链: rows={}", rows);
            return true;
        }

        return false;
    }
}
